////////////////////////////////////////////////////////////////////////////////
//                                                                            //
//  Filename:    SuperDiamondMineGuts.cpp                                     //
//  Developer:   George Chapman                                               //
//  Create date: 7/20/2003                                                    //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////
//                                                                            //
//  Description: This module contains the implementation for the guts of      //
//               the Super Diamond Mine slot machine.  Super Diamond Mine     //
//               is a five reel slot machine that has a unique feature where  //
//               diamonds can slide across the reels to change the payouts.   //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////
//        Copyright (c) 2003 - Realtime Gaming.  All rights reserved.         //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////
// Include Files                  //
////////////////////////////////////
#include <stdlib.h>
#include "lib.h"
#include "slotguts.h"
#include "SuperDiamondMineGuts.h"
#include "slotspin.h"
#include "getrand.h"
#include "sltsql.h"
#include "slotconn.h"
#include "opts.h"
#include "log.h"

// Uncomment the #define to override the generated spin
// values and pull from an array of known stop positions
// that will produce certain payouts.  This gives us the 
// the opportunity to test our classifyStops method a lot
// better.  The spins for the 97% payout gut have been
// identified.

////////////////////////////////////
// Local Enumerations             //
////////////////////////////////////

// Super Diamond Mine is a little different in
// that "combination" icons can count as a hit
// towards more than one total (i.e. A diamond 
// with a single bar counts as both a diamond and
// a bar).  So, we have categories to help describe
// what an icon can be counted as.
enum IconCategory
{
    IC_NONE,
    IC_SINGLE_BAR,
    IC_DOUBLE_BAR,
    IC_TRIPLE_BAR,
    IC_DYNAMITE,
    IC_MINE,
    IC_DIAMOND,
    IC_NUM_CATEGORIES
};

// This enumeration matches the wheel values in the database.
enum WheelContents 
{
	BLANK = 0,
    COAL,
    DD,
    DU,
    SINGLE_BAR,
    SINGLE_BAR_DD,
    SINGLE_BAR_DU,
    DOUBLE_BAR,
    DOUBLE_BAR_DD,
    DOUBLE_BAR_DU,
    TRIPLE_BAR,
    TRIPLE_BAR_DD,
    TRIPLE_BAR_DU,
    DYNAMITE,
    MINE
};

// This enumeration describes the different possible outcomes for 
// payouts on the machine.  I have split the payouts into three 
// categories:
//
//  Category 1:
//      There is a match of 3 or more diamonds AND 3 or more other icons.
//
//  Category 2:
//      There is a match of 3 or more diamonds, but not a match on 3 or more 
//      other icons.  
//
//  Category 3:
//      There is a match of 3 or more icons, without 3 or more diamonds.
// 
//  Based on these three categories, it is easy to derive a formula to 
//  calculate the spin result in classifyStops() as opposed to having 
//  a giant if/then/else mess.
enum SpinResult
{
	EVT_LOSER = 0,

    // Category 1 Payout Evaluation
    EVT_CATEGORY_1_PAYOUT_RANGE_BEGIN,

    // 1 - 3
    EVT_3_DIAMONDS_AND_3_SINGLE_BAR = EVT_CATEGORY_1_PAYOUT_RANGE_BEGIN,
    EVT_3_DIAMONDS_AND_3_DOUBLE_BAR,
    EVT_3_DIAMONDS_AND_3_TRIPLE_BAR,

    // 4 - 6
    EVT_3_DIAMONDS_AND_4_SINGLE_BAR,
    EVT_3_DIAMONDS_AND_4_DOUBLE_BAR,
    EVT_3_DIAMONDS_AND_4_TRIPLE_BAR,

    // 7 - 9
    EVT_3_DIAMONDS_AND_5_SINGLE_BAR,
    EVT_3_DIAMONDS_AND_5_DOUBLE_BAR,
    EVT_3_DIAMONDS_AND_5_TRIPLE_BAR,

    // 10 - 12
    EVT_4_DIAMONDS_AND_3_SINGLE_BAR,
    EVT_4_DIAMONDS_AND_3_DOUBLE_BAR,
    EVT_4_DIAMONDS_AND_3_TRIPLE_BAR,

    // 13 - 15
    EVT_4_DIAMONDS_AND_4_SINGLE_BAR,
    EVT_4_DIAMONDS_AND_4_DOUBLE_BAR,
    EVT_4_DIAMONDS_AND_4_TRIPLE_BAR,

    // 16 - 18
    EVT_4_DIAMONDS_AND_5_SINGLE_BAR,
    EVT_4_DIAMONDS_AND_5_DOUBLE_BAR,
    EVT_4_DIAMONDS_AND_5_TRIPLE_BAR,

    // 19 - 21
    EVT_5_DIAMONDS_AND_3_SINGLE_BAR,
    EVT_5_DIAMONDS_AND_3_DOUBLE_BAR,
    EVT_5_DIAMONDS_AND_3_TRIPLE_BAR,

    // 22 - 24
    EVT_5_DIAMONDS_AND_4_SINGLE_BAR,
    EVT_5_DIAMONDS_AND_4_DOUBLE_BAR,
    EVT_5_DIAMONDS_AND_4_TRIPLE_BAR,

    // 25 - 27
    EVT_5_DIAMONDS_AND_5_SINGLE_BAR,
    EVT_5_DIAMONDS_AND_5_DOUBLE_BAR,
    EVT_5_DIAMONDS_AND_5_TRIPLE_BAR,

    // Category 2 Payout Evaluation
    EVT_CATEGORY_2_PAYOUT_RANGE_BEGIN,

    // 28 - 30
    EVT_3_DIAMONDS = EVT_CATEGORY_2_PAYOUT_RANGE_BEGIN,
    EVT_4_DIAMONDS,
    EVT_5_DIAMONDS,


    // Category 3 Payout Evaluation
    EVT_CATEGORY_3_PAYOUT_RANGE_BEGIN,

    // 31 - 35
    EVT_3_SINGLE_BAR = EVT_CATEGORY_3_PAYOUT_RANGE_BEGIN,
    EVT_3_DOUBLE_BAR,
    EVT_3_TRIPLE_BAR,
    EVT_3_DYNAMITE,
    EVT_3_MINE,

    // 36 - 40
    EVT_4_SINGLE_BAR,
    EVT_4_DOUBLE_BAR,
    EVT_4_TRIPLE_BAR,
    EVT_4_DYNAMITE,
    EVT_4_MINE,

    // 41 - 45
    EVT_5_SINGLE_BAR,
    EVT_5_DOUBLE_BAR,
    EVT_5_TRIPLE_BAR,
    EVT_5_DYNAMITE,
    EVT_5_MINE,

    NUMEVENTS
};

// Initialize static member data
const uint8 SuperDiamondMineGuts::m_nMaxDiamondMines = 7;

static int32 g_BigBonusNumGoodPicksWeights[] = 
{
    0,
    0,
    0,
    0,
    0,
    0,
    1,
    1,
    1,
    1,
    1,
    2,
    2,
    2,
    2,
    3,
    3,
    3,
    4,
    4,
    5
};

// Use sizeof operations so that if new payouts are added, we won't have to hunt
// the code and replace hard coded constants which used to represent the length of the 
// payout array.
const uint32 SuperDiamondMineGuts::m_nNumBigBonusNumGoodPicksWeights = 
    sizeof( g_BigBonusNumGoodPicksWeights ) /  sizeof( g_BigBonusNumGoodPicksWeights[0] );


// Payout array for the Super Diamond Mine bonus game.
static int32 g_BigBonusPayouts[] =
{
    22,
    21,
    21,
    21,
    21,
    20,
    19,
    18,
    17,
    16,
    15,
    15,
    15,
    15,
    14,
    14,
    14,
    13,
    13,
    13,
    12,
    11,
    10,
    9,
    8,
    8,
    8,
    8,
    8,
    8,
    8,
    8,
    7,
    7,
    7,
    7,
    7,
    7,
    7,
    7,
    7,
    6,
    6,
    6,
    6,
    5,
    5,
    5,
    5,
    5,
    5,
    5,
    5,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    4,
    3,
    3,
    3,
    3,
    3,
    3,
    3,
    3,
    3,
    3,
    2,
    2,
    2,
    2,
    2,
    2,
    2,
    2,
    2,  
    2,
    2,
    2,
    2,
    2,
    2,
    2,
    2,
    2,
    2,
    2,
    2,  
    2,
    2,
    2,
    2,
    2,  
    2
};

// Use sizeof operations so that if new payouts are added, we won't have to hunt
// the code and replace hard coded constants which used to represent the length of the 
// payout array.
const uint32 SuperDiamondMineGuts::m_nNumBigBonusPayouts = 
    sizeof( g_BigBonusPayouts ) /  sizeof( g_BigBonusPayouts[0] );

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  SuperDiamondMineGuts()                                                           //
//     Default constructor used to initialize the slot machine.                      //
//                                                                                   //
//     Parameters:                                                                   //
//      None                                                                         //
//                                                                                   //
//     Return Value:                                                                 //
//      None                                                                         //
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
SuperDiamondMineGuts::SuperDiamondMineGuts() : SlotGuts(),
    m_bReadyToFinish(false),
    m_nTotalBet(0)
{
}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  ~SuperDiamondMineGuts()                                                          //
//     Virtual destructor.                                                           //
//                                                                                   //
//     Parameters:                                                                   //
//      None                                                                         //
//                                                                                   //
//     Return Value:                                                                 //
//      None                                                                         //
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
SuperDiamondMineGuts::~SuperDiamondMineGuts()
{
    clearWheelHouse( &m_OriginalWheelHouseCopy );
}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  isValidGuts()                                                                    //
//     Every slot machine has a range of valid guts id.  This function tests to      //
//     ensure the guts id for this slot is valid.                                    //
//                                                                                   //
//     Parameters:                                                                   //
//                                                                                   //
//                                                                                   //
//     Return Value:                                                                 //
//      bool8                               - True or false inficating if the guts   //
//                                            id is valid.                           //  
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
bool8 
SuperDiamondMineGuts::isValidGuts()
{
    return (getGutsId() >= 16500 && getGutsId() < 16600);
}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  load()                                                                           //
//     This is an override of the framework method.  We call the base class load to  //
//     ensure the reels get set up, and then we copy them here.                      //
//                                                                                   //
//     Parameters:                                                                   //
//      info                                - An Info pointer.  We don't use it,     //
//                                             but we do pass it to the base class.  // 
//                                                                                   //
//     Return Value:                                                                 //
//      bool8                               - True or false inficating if the load   //
//                                            succeeded.                             //  
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
bool8   
SuperDiamondMineGuts::load(Info *info)
{
    // Call the base class to ensure that the reels get loaded.
    bool8 bReturn = BASE::load( info );

    if ( bReturn )
    {
        // The first thing we want to do is preserve a copy of the 
        // wheel house in memory.  Because we like to cascade diamonds
        // arond the wheelbox, we end up altering the reel prior to
        // classifying the stops.  Keeping a copy of the wheel house gives
        // use something to restore back to.  
        copyWheelHouse( &m_wheelHouse, &m_OriginalWheelHouseCopy );
    }

    return bReturn;
}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  classifyStops()                                                                  //
//     This method is an override for the slot framework.  It is called to           //
//     evaluate a given line and determine if it is a winner.                        //
//                                                                                   //
//     Parameters:                                                                   //
//      stops                           -  This is an array of unsigned ints         //
//                                          indicating the positions on the reel     //
//                                          which are being evaluated.               //
//      bet                             -  This amount indicates the bet.            //
//      line                            -  This is the index of the pay line being   //
//                                          evaluated.                               //
//                                                                                   //
//     Return Value:                                                                 //
//      uint8                           -  This is a result which matches up to      //
//                                         values in the SpinResult enumeration.     //
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
uint8
SuperDiamondMineGuts::classifyStops(uint8* stops,uint8 bet, uint8 line)
{
	uint8 result        = EVT_LOSER;
	uint8 testIcon      = 0;
    uint8 iconCategory  = IC_NONE;
    uint8 iconHitCounts[IC_NUM_CATEGORIES];

    // Initialize the array which will hold the number of icon category hits
    // on the line.
    memset( iconHitCounts, 0, sizeof(iconHitCounts) );

    // First, let's count up the number of diamonds on the row.
	for ( uint8 i = 0 ; i < getReelCount(); ++i )
    {
		testIcon = getIcon(i, stops[i])->iconid;

        // Test to see if the icon has a diamond.
        // If so, bump up the diamond count.
		if(iconHasDiamond(testIcon))
		{
			++iconHitCounts[IC_DIAMOND];
		}

        iconCategory = getIconCategory( testIcon );
        
        // The getIconCategory() function favors other icon categories
        // over diamonds.  Therefore, if an icon is both a bar and a diamond,
        // it will categorize the icon as a bar.  That is why we have a separate
        // test above to increment the hit count on diamonds, and why we
        // do NOT increment the diamond category below.
        if ( iconCategory != IC_DIAMOND )
        {
            ++iconHitCounts[iconCategory];
        }
	}

    uint8  maxHits = 0;
    uint8  maxHitIconCategory = IC_NONE;

    // The higher categories are worth more in case
    // of a tie so start looking at hit counts going 
    // backwards through the category list.  If a higher
    // hit count is found, then mark that category and it's
    // number of hits.
    for ( i = IC_DIAMOND - 1; i > IC_NONE; --i )
    {
        if ( iconHitCounts[i] > maxHits )
        {
            maxHits = iconHitCounts[i];
            maxHitIconCategory = i;
        }
    }

    // Test for Category 1 Payout
    if ( iconHitCounts[IC_DIAMOND] >= 3 && maxHits >= 3)
    {
        // According to Josh, based on the way the icons are laid out on the reel,
        // we are NEVER going to have a case where icons appear behind a DYNAMITE or a MINE
        // because of cascading.  As a result, we should NEVER have 3 or more diamonds
        // AND 3 or more MINEs or DYNAMITEs.  BUT, just in case, lets ASSERT if we do...
        ASSERT( maxHitIconCategory != IC_MINE );
        ASSERT( maxHitIconCategory != IC_DYNAMITE );

        result = EVT_CATEGORY_1_PAYOUT_RANGE_BEGIN + 
                 ( ( iconHitCounts[IC_DIAMOND] - 3 ) * 9 ) + 
                 ( ( maxHits - 3 ) * 3 ) + ( maxHitIconCategory - 1 );
    }
    // Test for Category 2 Payout
    else if ( iconHitCounts[IC_DIAMOND] >= 3 )
    {
        result = EVT_CATEGORY_2_PAYOUT_RANGE_BEGIN + 
                 ( iconHitCounts[IC_DIAMOND] - 3 );
    }
    // Test for Category 3 Payout
    else if ( maxHits >= 3 )
    {
        result = EVT_CATEGORY_3_PAYOUT_RANGE_BEGIN + 
                 ( maxHits - 3 ) * 5 + 
                 ( maxHitIconCategory - 1 );
    }

	return result;
}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  preSpinClassifyResults()                                                         //
//     This is an override of a framework function which is called after the         //
//     wheel stops but before the results are classified.  This gives us an          //
//     opportunity to alter the reel a bit to account for these diamonds that        //
//     shift all over the place!                                                     //
//                                                                                   //
//     Parameters:                                                                   //
//      spin                       -  A slot spin pointer with all sorts of useful   //
//                                    information.                                   //
//                                                                                   //
//     Return Value:                                                                 //
//      None                                                                         //
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
void
SuperDiamondMineGuts::preSpinClassifyResults(SlotSpin* spin)
{
    
	SlotGuts::preSpinClassifyResults(spin);
	
	// This game is a little different in the fact that the reels
    // can actually change icon values because the diamonds can float
    // up and down the reels.  So what we are doing is overriding 
    // this function BEFORE we classify the stops (but after the stops
    // have been selected).  At this point, we know what icons are going
    // to be evaluated.  So, we modify the reel so that it now represents
    // the diamonds shifting positions (if they are present).  Then,
    // upon returning from this function, classifySpin() and classifyStops()
    // will get called, and the evaluation will use the new reel values.
    // After the stops have been classified, we will restore the reels to their
    // original definition in postSpinClassifyResults().
    for ( uint8 i = 0; i < m_wheelHouse.size(); ++i )
    {
        ISlotReel* pModifyReel = m_wheelHouse[i];

        uint8 stopPosition = spin->getStops()[i];
        uint8 initialCheckPosition;
        uint8 modPosition1;
        uint8 modPosition2;
        uint8 diamondIcon;

        // Even reels only have diamonds pointing down...
        if ( i % 2 == 0 )
        {
            // If this is an even reel, then we will start at the "stop" position.  The stop position
            // represents the icon that will be visible at the top of the reel when it stops.  Therefore
            // we will start looking at the top of the reel, check the space below, and the space below that
            // to see if a diamond will float into its position.
            initialCheckPosition = stopPosition;
            modPosition1         = ( stopPosition + 1 ) % pModifyReel->size();
            modPosition2         = ( stopPosition + 2 ) % pModifyReel->size();
            diamondIcon          = DD;
        }
        // Odd reels have diamonds pointing up...
        else
        {
            // If this is an odd reel, then we will start at the bottom of the reel, two positions after the "stop" position.  
            // The stop position represents the icon that will be visible at the top of the reel when it stops.  Therefore
            // we will start looking at the bottom of the reel, check the space above, and the space above that
            // to see if a diamond will float into its position.            
            initialCheckPosition = ( stopPosition + 2 ) % pModifyReel->size();
            modPosition1         = ( stopPosition + 1 ) % pModifyReel->size();
            modPosition2         = stopPosition;
            diamondIcon          = DU;
        }
        
        // If there is a diamond in the initial position, try and add a diamond to the next position.
        if ( iconHasDiamond(((*pModifyReel)[initialCheckPosition])->iconid ) )
        {
            ( (*pModifyReel)[ modPosition1 ])->iconid = addDiamondToIcon( ((*pModifyReel)[ modPosition1 ])->iconid, diamondIcon );
        }

        // If there is a diamond in the initial position, or the middle of the reel, then see if the diamond floated to the
        // other side of the reel.
        if ( iconHasDiamond(((*pModifyReel)[ modPosition1 ])->iconid ) || iconHasDiamond(((*pModifyReel)[initialCheckPosition])->iconid )  )
        {
            ( (*pModifyReel)[ modPosition2 ])->iconid = addDiamondToIcon( ((*pModifyReel)[ modPosition2 ])->iconid, diamondIcon );
        }
    }
}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  postSpinClassifyResults()                                                        //
//     This is an override of a framework function which is called after the         //
//     wheel stops and after the results are classified.  This gives us an           //
//     opportunity to reset the reel back to its original state.                     //
//     shift all over the place!                                                     //
//                                                                                   //
//     Parameters:                                                                   //
//      spin                       -  A slot spin pointer with all sorts of useful   //
//                                    information.                                   //
//                                                                                   //
//     Return Value:                                                                 //
//      None                                                                         //
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
void
SuperDiamondMineGuts::postSpinClassifyResults(SlotSpin* spin)
{
    // This is called after the stops have been classified.  Go ahead and restore the reel values.
    copyWheelHouse( &m_OriginalWheelHouseCopy, &m_wheelHouse );
}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  classifyScatters()                                                               //
//     This method is an override for the slot framework.  It is called to           //
//     evaluate the entire wheel house and see if bonuses have been earned.          //
//                                                                                   //
//     Parameters:                                                                   //
//      spin                       -  A slot spin pointer with all sorts of useful   //
//                                    information.                                   //
//                                                                                   //
//     Return Value:                                                                 //
//      None                                                                         //
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
void
SuperDiamondMineGuts::classifyScatters(SlotSpin *spin)
{
    int8   diamondCascadePos[5];

    int32 nDiamondCounter = m_subGame->getData( SGIDX_SDM_DIAMOND_COUNTER );

    for ( uint8 i = 0; i < sizeof( diamondCascadePos ) / sizeof ( diamondCascadePos[0] ); ++i )
    {
        diamondCascadePos[i] = -1;
    }

    // Update the running bet totals so we can calculate the average bet
    // if we go into a bonus game.  Fortunately getData() returns zero
    // if these varaibles have not been entered/initialized in the sub game
    // data.
    int32 nRunningBetTotal = m_subGame->getData( SGIDX_SDM_RUNNING_BET_TOTAL );
    int32 nRunningBetCount = m_subGame->getData( SGIDX_SDM_RUNNING_BET_COUNT );

    nRunningBetTotal += spin->getTotalBet();
    ++nRunningBetCount;

    m_subGame->setServerOnly( SGIDX_SDM_RUNNING_BET_TOTAL, true );
    m_subGame->update( SGIDX_SDM_RUNNING_BET_TOTAL, nRunningBetTotal );
    m_subGame->setServerOnly( SGIDX_SDM_RUNNING_BET_COUNT, true );
    m_subGame->update( SGIDX_SDM_RUNNING_BET_COUNT, nRunningBetCount);

    bool8 bDiamondCascaded = false;

    // Iterate through each of the wheels...
    for ( i = 0; i < m_wheelHouse.size(); ++i )
    {
        ISlotReel* pReel   = m_wheelHouse[i];
        uint8 stopPosition = spin->getStops()[i];

        // Look at all the visible icons on the reel...
        for ( uint8 j = 0; j < 3; ++j )
        {
            uint8 icon = getIcon(i, ( ( stopPosition + j ) % pReel->size() ) )->iconid;
            bool8 bIconHasDiamond = iconHasDiamond( icon );

            // If the icon has a diamond...
            if ( bIconHasDiamond )
            {
                // Bump up the diamond counter.
                ++nDiamondCounter;

                // If it is an even reel...
                if ( i % 2 == 0 )
                {
                    // Since we are looking "down" the reel as soon as we 
                    // see a diamond, if it is not the end of the visible reel,
                    // we have found a diamond that will cascade.  We ignore the
                    // end of the visible reel because a diamond can not cascade
                    // down from there.
                    if ( j < 2 )
                    {
                        bDiamondCascaded = true;
                    }
                }
                // else it is an odd reel...
                else
                {
                    // Since we are looking "down" the reel, we keep looking 
                    // until we get to the end to find the furthest point a diamond
                    // will cascade up from.  We ignore the top of the reel because
                    // a diamond can't cascade up from there.
                    if ( j > 0 )
                    {
                        bDiamondCascaded = true; 
                    }
                }
            }
        }

    }

    if ( nDiamondCounter > 99 )
    {
        nDiamondCounter = 99;
    }
    
    m_subGame->update( SGIDX_SDM_DIAMOND_COUNTER, nDiamondCounter );

    uint8 bgValue = BGNONE;

    // Default to "No bonus game for you!  Come back, one spin!", sayeth the Slot Nazi...
    m_bReadyToFinish = true;

    if ( bDiamondCascaded )
    {
        // Set up the bonus game data so diamonds can be animated cascading on the client.
        bgValue += BGCASCADE;

        // Let the framework know that we ain't done yet!
        m_bReadyToFinish = false;
    }
    // Enter the big bonus round with the diamond mine!
    if ( nDiamondCounter >= 99 )
    {
        // Cache off the bet - it will get lost in the bonus game.
		m_nTotalBet = spin->getTotalBet(); 

        bgValue += BGDIAMONDMINE;
		initializeDiamondMineBonusGame();

        // Let the framework know that we ain't done yet!
        m_bReadyToFinish = false;
    }

	m_subGame->update(SGIDX_SDM_BONUSGAME,bgValue);

}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  initializeDiamondMineBonusGame()                                                 //
//     This method is called locally to set up the bonus game.                       //
//                                                                                   //
//     Parameters:                                                                   //
//      None                                                                         //
//                                                                                   //
//     Return Value:                                                                 //
//      None                                                                         //
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
void
SuperDiamondMineGuts::initializeDiamondMineBonusGame()
{
    // Figure out how many mines the gnome can visit before getting a cave in.
    m_subGame->setServerOnly( SGIDX_SDM_NUMGOODPICKS, true );
    m_subGame->update( SGIDX_SDM_NUMGOODPICKS, 
                       getRandomFromArray(g_BigBonusNumGoodPicksWeights, m_nNumBigBonusNumGoodPicksWeights) );
    m_subGame->setServerOnly(SGIDX_SDM_MINES_PICKED, true);
    m_subGame->update(SGIDX_SDM_MINES_PICKED, 0);


    // Initialize all the picks.
    m_subGame->update( SGIDX_SDM_PICK_ONE,   SGNOTPICKED );
    m_subGame->update( SGIDX_SDM_PICK_TWO,   SGNOTPICKED );
    m_subGame->update( SGIDX_SDM_PICK_THREE, SGNOTPICKED );
    m_subGame->update( SGIDX_SDM_PICK_FOUR,  SGNOTPICKED );
    m_subGame->update( SGIDX_SDM_PICK_FIVE,  SGNOTPICKED );
    m_subGame->update( SGIDX_SDM_PICK_SIX,   SGNOTPICKED );
    m_subGame->update( SGIDX_SDM_PICK_SEVEN, SGNOTPICKED );

    // Initialize all the values.
    m_subGame->update( SGIDX_SDM_VALUE_ONE,   0 );
    m_subGame->update( SGIDX_SDM_VALUE_TWO,   0 );
    m_subGame->update( SGIDX_SDM_VALUE_THREE, 0 );
    m_subGame->update( SGIDX_SDM_VALUE_FOUR,  0 );
    m_subGame->update( SGIDX_SDM_VALUE_FIVE,  0 );
    m_subGame->update( SGIDX_SDM_VALUE_SIX,   0 );
    m_subGame->update( SGIDX_SDM_VALUE_SEVEN, 0 );

    // Done???  We're just getting started...
    m_subGame->update( SGIDX_SDM_LAST_MINE_SELECTED, -1);
	m_subGame->update(SGIDX_SDM_ISDONE, 0);
}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  playBonusGame()                                                                  //
//     This method is used to handle all the bonus game messages.                    //
//                                                                                   //
//     Parameters:                                                                   //
//      bonusGameType                  - Which bonus game are we in?                 //
//      bonusDataCount                 - How many bonusData elements are there?      //
//      bonusData                      - An array of int32s worth of data.           //
//                                                                                   //
//     Return Value:                                                                 //
//      None                                                                         //
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
void 
SuperDiamondMineGuts::playBonusGame(uint8 bonusGameType, int32 bonusDataCount, int32 *bonusData)
{
	bool8 bProblem = false;
    int64 nCurrentGameNum = 0;

	m_bReadyToFinish = false;

    SlotSpin* pSpin = getSlotSpin( getSlotSpinCount() - 1 );

	if ( pSpin )
	{
		nCurrentGameNum = pSpin->getGameNum();
    }
	else
	{
		bProblem = true;
		Log::msgWarning("Super Diamond Mine - Can't load spin data!");
	}

	if (! (m_subGame->getData(SGIDX_SDM_BONUSGAME) & BGDIAMONDMINE ) )
	{
		// Trying to play when you're not in a bonus game!
		bProblem = true;
		Log::msgWarning("Super Diamond Mine - Trying to play bonus game when not in a bonus game: %d", nCurrentGameNum);
	}

	if ( bonusGameType != BGDIAMONDMINE )
	{
		bProblem = true;
		Log::msgWarning("Super Diamond Mine - Trying to play with bad bonus game type: %d", bonusGameType);
	}

    // We are only expecting one thing back from the client:  A mine ID from 1 to 7
    // (the maximum number of mines).
	if ( bonusDataCount != 1 || (bonusData[0] < 0 || bonusData[0] >= m_nMaxDiamondMines ) )
	{
		// We should only have one pick, and it should be between 0 and 6	
		bProblem = true;
		Log::msgWarning("Super Diamond Mine - Trying to play with funky pick (%d): %d", bonusData[0], nCurrentGameNum);
	}

    // If we haven't logged an error...
	if (!bProblem)
	{
		m_nTotalBet       = pSpin->getTotalBet();

		int32 whichMine = bonusData[0];
        int32 numGoodPicks = m_subGame->getData( SGIDX_SDM_NUMGOODPICKS );
        int32 numMinesPicked = m_subGame->getData( SGIDX_SDM_MINES_PICKED );
        int32 averageBet = 0;
        int32 pickValue = 0;
         
        // Defect 1990 - GChapman - 10/14/2003
        // The running bet count should always be > 0.  However,
        // there was a bug in the slot framework that resulted in
        // it being 0.  So, to avoid a divide by zero (ever) we 
        // wrapped this check around the operation.
        if ( m_subGame->getData( SGIDX_SDM_RUNNING_BET_COUNT ) > 0 )
        {
            averageBet = m_subGame->getData( SGIDX_SDM_RUNNING_BET_TOTAL ) / m_subGame->getData( SGIDX_SDM_RUNNING_BET_COUNT );
        }
        else
        {
            ASSERT(false);
        }

        // Check to see if somehow the same mine was picked more than once.
        // Our client should not allow this, but...
        if ( m_subGame->getData( SGIDX_SDM_PICK_ONE + ( whichMine * 2 ) ) == SGNOTPICKED )
        {
            // Check the number of mines picked already against the maximum number of good picks.
            // If all the good mines have been picked, this mine is a loser.
            if (  numMinesPicked >= numGoodPicks )
            {
				m_bReadyToFinish = true;
        
                // Set the mine they picked to be a zero credit loser.
                m_subGame->update( SGIDX_SDM_PICK_ONE + ( whichMine * 2 ), SGPICKEDLOSER );
                m_subGame->update( SGIDX_SDM_VALUE_ONE + ( whichMine * 2 ), 0 );

                // Go through the remaining unpicked mines and determine
                // a "lost value" for those mines.  Remember that there 
                // are always two bad mines, so since we just
                // hit one of the bad mines, one of the remaining mines
                // is also bad.
                int32 nNextBadMine = getrand() % (m_nMaxDiamondMines-numGoodPicks-1);

                // This code depends on the enums being structured in
                // such a way that the pairs are together, and all pairs
                // are ordered sequentially starting with SGIDX_SDM_PICK_ONE
                // We cycle through all the mines looking for ones that haven't
                // been picked.  Each time we find one, we check to see if
                // the next bad mine is zero.  If not, we'll use this mine as 
                // a potential winner.  Compute a value for it.  If next bad mine
                // is zero, than this was the other bad mine.  
                for ( int32 i = 0; i < m_nMaxDiamondMines; ++i )
                {
                    if ( m_subGame->getData( SGIDX_SDM_PICK_ONE + ( i * 2 ) ) == SGNOTPICKED )
                    {
                        if ( nNextBadMine == 0 )
                        {
                            m_subGame->update( SGIDX_SDM_PICK_ONE + ( i * 2 ), SGNOTPICKEDLOSER );
                            m_subGame->update( SGIDX_SDM_VALUE_ONE + ( i * 2 ), 0 );
                        }
                        else
                        {
                			int32 notpickedValue = getRandomFromArray(g_BigBonusPayouts, m_nNumBigBonusPayouts)*(averageBet);
                            m_subGame->update( SGIDX_SDM_PICK_ONE + ( i * 2 ), SGNOTPICKEDWINNER );
                            m_subGame->update( SGIDX_SDM_VALUE_ONE + ( i * 2 ), notpickedValue );
                        }

                        --nNextBadMine;
                    }

                }

                // Update the sub game data.
                ++numMinesPicked;
				m_subGame->setServerOnly(SGIDX_SDM_NUMGOODPICKS, true );
				m_subGame->update(SGIDX_SDM_NUMGOODPICKS, 0 );
				m_subGame->update(SGIDX_SDM_ISDONE, 1 );
                m_subGame->setServerOnly( SGIDX_SDM_RUNNING_BET_TOTAL, true );
                m_subGame->update( SGIDX_SDM_RUNNING_BET_TOTAL, 0 );
                m_subGame->setServerOnly( SGIDX_SDM_RUNNING_BET_COUNT, true );
                m_subGame->update( SGIDX_SDM_RUNNING_BET_COUNT, 0 );
                m_subGame->update( SGIDX_SDM_DIAMOND_COUNTER, 0 );
                m_subGame->setServerOnly( SGIDX_SDM_MINES_PICKED, true );
                m_subGame->update( SGIDX_SDM_MINES_PICKED, numMinesPicked );
                m_subGame->update( SGIDX_SDM_LAST_MINE_SELECTED, whichMine);
            }
            // They still have the ability to pick a new mine.
            // Check and make sure they do.
            else
            {
			    // Figure out the payout first, then put it in the right place
			    pickValue = getRandomFromArray(g_BigBonusPayouts, m_nNumBigBonusPayouts)*(averageBet);

                // This code depends on the enums being structured in
                // such a way that the pairs are together, and all pairs
                // are ordered sequentially starting with SGIDX_SDM_PICK_ONE
                ++numMinesPicked;
                m_subGame->update( SGIDX_SDM_PICK_ONE + ( whichMine * 2 ), SGPICKEDWINNER );
                m_subGame->update( SGIDX_SDM_VALUE_ONE + ( whichMine * 2 ), pickValue );
                m_subGame->setServerOnly( SGIDX_SDM_MINES_PICKED, true );
                m_subGame->update( SGIDX_SDM_MINES_PICKED, numMinesPicked );
                m_subGame->update( SGIDX_SDM_LAST_MINE_SELECTED, whichMine);
            }
        }
        // Log an error if the same mine is picked more than once.
        else
        {
			bProblem = true;
			Log::msgWarning("Super Diamond Mine - Picked the same mine more than once.  (Mine = %d): %d", whichMine, nCurrentGameNum);
        }

		//Write to the database
		createBonusGame(nCurrentGameNum, 10, numMinesPicked, BGDIAMONDMINE, whichMine+1, pickValue*(m_info->creditValue) );

        //If we're done, finish the game
		if( isGameComplete() )
		{
			completeGame();
		}
	}
}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  isGameComplete()                                                                 //
//     This is an accessor method called by the framework to determine if the        //
//     slot machine is finished with this spin.                                      //
//                                                                                   //
//     Parameters:                                                                   //
//      None                                                                         //
//                                                                                   //
//     Return Value:                                                                 //
//                                                                                   //
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
bool8 
SuperDiamondMineGuts::isGameComplete()
{
    return m_bReadyToFinish;
}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  saveGame()                                                                       //
//     This method is called by the framework to persist data back to the database.  //
//                                                                                   //
//     Parameters:                                                                   //
//      None                                                                         //
//                                                                                   //
//     Return Value:                                                                 //
//      bool8                       - True or false indicating if the save worked.   //
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
bool8	
SuperDiamondMineGuts::saveGame()
{
	//Base class
	bool8 result = SlotGuts::saveGame();

	if(result && !m_bReadyToFinish)
	{
        // get the gamenum
		int64 nCurrentGameNum = (getSlotSpin(getSlotSpinCount()-1))->getGameNum();
		
        // Cascading diamonds
        if ( m_subGame->getData(SGIDX_SDM_BONUSGAME) == BGCASCADE )
        {
            m_bReadyToFinish = true;
        }

		// Diamond Mine bonus game
        if(m_subGame->getData(SGIDX_SDM_BONUSGAME) & BGDIAMONDMINE)
		{
			if(!createBonusGame( nCurrentGameNum, 10, 0, BGDIAMONDMINE, m_subGame->getData(SGIDX_SDM_NUMGOODPICKS), 0) )
			{
				result = false;
			}
		}
	}

	return result;
}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  iconHasDiamond()                                                                 //
//     This method is used to determine if the icon has a diamond on it.             //
//                                                                                   //
//     Parameters:                                                                   //
//      icon                       -  This is the source icon, the one being tested  //
//                                      to see if it has a diamond.                  //
//                                                                                   // 
//     Return Value:                                                                 //
//      bool8                      -  The function returns true or false indicating  //
//                                    the presence of a diamond.                     //
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
bool8
SuperDiamondMineGuts::iconHasDiamond(uint8 icon) const 
{
    bool8 bReturn = false;

    switch (icon)
    {
        case DD:
        case DU:
        case SINGLE_BAR_DD:
        case SINGLE_BAR_DU:
        case DOUBLE_BAR_DD:
        case DOUBLE_BAR_DU:
        case TRIPLE_BAR_DD:
        case TRIPLE_BAR_DU:
            bReturn = true;
            break;

        case BLANK:
        case COAL:
        case SINGLE_BAR:
        case DOUBLE_BAR:
        case TRIPLE_BAR:
        case DYNAMITE:
        case MINE:
            bReturn = false;
            break;

        default:
            // This means a new wheel icon has been added.
            // This function needs to be updated to account
            // for it.
            ASSERT(false);
            bReturn = false;
    }

    return bReturn;
}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  getIconCategory()                                                                //
//     This method is used to determine the category of an icon.  Since some icons   //
//     can be both diamonds and something else, this helps us to determine the       //
//     the category for the purpose of counting hits.  For example, a single         //
//     bar and a single bar with diamonds will result in two hits on a single        //
//     bar and one hit on a diamond.  It should be noted that if the icon  being     //
//     tested has both a diamond and another type, the function will ALWAYS report   //
//     the other type as the category.  We use another function, iconHasDiamond()    //
//     to check for diamonds.                                                        // 
//                                                                                   //
//     Parameters:                                                                   //
//      icon                       -  This is the source icon, the one being tested  //
//                                      to see its category.                         //
//                                                                                   // 
//     Return Value:                                                                 //
//      uint8                      -  The function returns a value from the          //
//                                    IconCategory enumeration.                      //
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
uint8
SuperDiamondMineGuts::getIconCategory(uint8 icon ) const
{
    uint8 result = IC_NONE;

    switch (icon)
    {
        case BLANK:
        case COAL:
            result = IC_NONE;
            break;
    
        case DD:
        case DU:
            result = IC_DIAMOND;
            break;

        case SINGLE_BAR:
        case SINGLE_BAR_DD:
        case SINGLE_BAR_DU:
            result = IC_SINGLE_BAR;
            break;

        case DOUBLE_BAR:
        case DOUBLE_BAR_DD:
        case DOUBLE_BAR_DU:
            result = IC_DOUBLE_BAR;
            break;

        case TRIPLE_BAR:
        case TRIPLE_BAR_DD:
        case TRIPLE_BAR_DU:
            result = IC_TRIPLE_BAR;
            break;

        case DYNAMITE:
            result = IC_DYNAMITE;
            break;

        case MINE:
            result = IC_MINE;
            break;

        default:
            // If we hit this, it means a new icon type has been
            // added and we have not classified its category here.
            ASSERT(false);
            break;
    }

    return result;
}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  addDiamondToIcon()                                                               //
//     This method is used to determine if a diamond can float behind another icon,  //
//     and change it to an icon with a diamond.                                      //
//                                                                                   //
//     Parameters:                                                                   //
//      icon                       -  This is the source icon, the one being tested  //
//                                      to see if a diamond can be added to it.      //
//      diamondIcon                -  This is the type of diamond that is floating   //
//                                    over it (DD is diamond down, DU is diamond up).//
//                                                                                   // 
//     Return Value:                                                                 //
//      uint8                      -  The function returns the result of trying to   //
//                                    add a diamond to the icon.  If it can not be   //
//                                    done, the original icon is returned unchanged. // 
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
uint8
SuperDiamondMineGuts::addDiamondToIcon( uint8 icon, uint8 diamondIcon )
{
    uint8 returnIcon = BLANK;

    ASSERT( diamondIcon == DD || diamondIcon == DU );

    switch ( icon )
    {
        case BLANK:
        case COAL:
        case DD:
        case DU:
        case SINGLE_BAR_DD:
        case SINGLE_BAR_DU:
        case DOUBLE_BAR_DD:
        case DOUBLE_BAR_DU:
        case TRIPLE_BAR_DD:
        case TRIPLE_BAR_DU:
        case DYNAMITE:
        case MINE:

            // For any of these icons, a diamond
            // can not be added to it.
            returnIcon = icon;
            break;

        case SINGLE_BAR:
            if ( diamondIcon == DD )
            {
                returnIcon = SINGLE_BAR_DD;
            }
            else 
            {
                returnIcon = SINGLE_BAR_DU;
            }
            break;

        case DOUBLE_BAR:
            if ( diamondIcon == DD )
            {
                returnIcon = DOUBLE_BAR_DD;
            }
            else 
            {
                returnIcon = DOUBLE_BAR_DU;
            }
            break;

        case TRIPLE_BAR:
            if ( diamondIcon == DD )
            {
                returnIcon = TRIPLE_BAR_DD;
            }
            else 
            {
                returnIcon = TRIPLE_BAR_DU;
            }
            break;

        default:
            // This means a new wheel icon has been added.
            // This function needs to be updated to account
            // for it.
            ASSERT(false);
            break;
    }

    return returnIcon;
}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  clearWheelHouse()                                                                //
//     This method takes a pointer to a wheel house and iterates through the reels,  //
//     deleting icons, and reels, and emptying the vectors.                          //
//                                                                                   //
//     Parameters:                                                                   //
//      pWheelHouse                -  A pointer to the wheel house to be cleared.    //
//                                                                                   //
//     Return Value:                                                                 //
//      None                                                                         //
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
void 
SuperDiamondMineGuts::clearWheelHouse( SlotWheelHouse* pWheelHouse )
{
    while ( pWheelHouse->size() > 0 )
    {
        ISlotReel *reel = pWheelHouse->back();

        while ( reel->size() > 0 )
        {
            SlotIcon *del = reel->back();
            delete [] del->iconName;
            delete del;
            reel->pop_back();
        }

        delete reel;

        pWheelHouse->pop_back();
    }
}

///////////////////////////////////////////////////////////////////////////////////////
//                                                                                   //
//  copyWheelHouse()                                                                 //
//     This method takes a source and target wheel house vector, clears the target,  //
//     and performs a deep copy of the elements.                                     //
//                                                                                   //
//     Parameters:                                                                   //
//      pSourceWheelHouse          -  The source wheel house used to copy.           //
//      pTargetWheelHouse          -  The target wheel house copied into.            //
//                                                                                   //
//     Return Value:                                                                 //
//      None                                                                         //
//                                                                                   //
///////////////////////////////////////////////////////////////////////////////////////
void 
SuperDiamondMineGuts::copyWheelHouse( SlotWheelHouse* pSourceWheelHouse, SlotWheelHouse* pTargetWheelHouse )
{
    // Make sure we have an empty target wheel house.
    clearWheelHouse( pTargetWheelHouse );

    // Iterate through each reel in the source wheel house.
    for ( uint8 i = 0; i < pSourceWheelHouse->size(); ++i )
    {
        ISlotReel* pCopyReel = new ISlotReel();
        ISlotReel* pSourceReel = (*pSourceWheelHouse)[i];

        // Iterate through every icon on the reel...
        for ( uint8 j = 0; j < pSourceReel->size(); ++j )
        {
            SlotIcon* icon = new SlotIcon();

            SlotIcon* originalIcon = (*pSourceReel)[j];
            icon->reelid = originalIcon->reelid;
            icon->iconid = originalIcon->iconid;
            icon->position = originalIcon->position;
            icon->weight = originalIcon->weight;
            icon->iconName = new char[strlen(originalIcon->iconName) + 1];
            strcpy(icon->iconName, originalIcon->iconName);

            // Add the new icon to our new reel.
            pCopyReel->push_back( icon );
        }

        // Push the new reel on to the copy wheel house. 
        pTargetWheelHouse->push_back( pCopyReel );
    }
}

#ifdef DEBUGSPIN
void SuperDiamondMineGuts::Debug_SetLoserStops( uint8* pStops )
{
	pStops[0] = 4;
	pStops[1] = 20;
	pStops[2] = 17;
	pStops[3] = 26;
	pStops[4] = 22;
}
#endif